package mini

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"reflect"
	"strings"
)

type ApiDocs struct {
	Route    string `json:"route"`
	Method   string `json:"method"`
	in       reflect.Type
	out      reflect.Type
	Request  []*docs `json:"request"`
	Response []*docs `json:"response"`
	Header   []*docs `json:"header"`
}

type docs struct {
	Field    string         `json:"field"`
	Classify string         `json:"classify"`
	Pos      []*apiPosition `json:"pos"`
	Valid    []string       `json:"valid"`
	Comment  string         `json:"comment"`
}

type apiPosition struct {
	Position string `json:"position"`
	Name     string `json:"name"`
}

var routerGroup []*ApiDocs

func AppendRoute(apiDocs *ApiDocs, in reflect.Type, out reflect.Type) {
	apiDocs.in = in
	apiDocs.out = out
	routerGroup = append(routerGroup, apiDocs)
}

func CreateDocs() {
	for k := range routerGroup {
		v := routerGroup[k]
		v.Header = buildDocs("", v.in, []string{"header"})
		v.Request = buildDocs("", v.in, []string{"uri", "form", "json", "xml"})
		v.Response = buildDocs("", v.out, []string{"uri", "form", "json", "xml"})
	}
}

func buildDocs(prefix string, value reflect.Type, tags []string, realField ...reflect.StructField) []*docs {
	value = removePtr(value)

	if value.Kind() != reflect.Struct {
		return baseClassifyDocs(value, tags, realField...)
	}

	var data []*docs
	for k := 0; k < value.NumField(); k++ {
		field := value.Field(k)
		switch field.Type.Kind() {
		case reflect.Ptr:
			if field.Anonymous {
				data = append(data, buildDocs(prefix, removePtr(field.Type), tags)...)
			} else {
				var positions = fieldPosition(field, tags)
				if len(positions) < 1 {
					continue
				}
				var validate = fieldValidate(field)
				if field.Type.Elem().Kind() == reflect.Struct {
					data = append(data, &docs{
						Field:    prefixName(prefix, field.Name),
						Classify: fmt.Sprintf("Object[%s]", removePtr(field.Type).Name()),
						Pos:      positions,
						Valid:    validate,
						Comment:  field.Tag.Get("comment"),
					})
					data = append(data, buildDocs(prefixName("--", prefixName(prefix, field.Name)), field.Type, tags)...)
				} else {
					data = append(data, buildDocs(prefix, removePtr(field.Type), tags, field)...)
				}
			}
		case reflect.Struct:
			if field.Anonymous {
				data = append(data, buildDocs(prefix, field.Type, tags)...)
			} else {
				var positions = fieldPosition(field, tags)
				if len(positions) < 1 {
					continue
				}
				var validate = fieldValidate(field)
				data = append(data, &docs{
					Field:    prefixName(prefix, field.Name),
					Classify: fmt.Sprintf("Object[%s]", field.Type.Name()),
					Pos:      positions,
					Valid:    validate,
					Comment:  field.Tag.Get("comment"),
				})
				data = append(data, buildDocs(prefixName("--", prefixName(prefix, field.Name)), field.Type, tags)...)
			}
		case reflect.String, reflect.Float64, reflect.Float32, reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Bool:
			var position = fieldPosition(field, tags)
			if len(position) < 1 {
				continue
			}
			var validate = fieldValidate(field)
			data = append(data, &docs{
				Field:    prefixName(prefix, field.Name),
				Classify: field.Type.Name(),
				Pos:      position,
				Valid:    validate,
				Comment:  field.Tag.Get("comment"),
			})
		case reflect.Slice:
			fieldClassify := removePtr(field.Type.Elem())
			var positions = fieldPosition(field, tags)
			if len(positions) < 1 {
				continue
			}
			var validate = fieldValidate(field)
			data = append(data, &docs{
				Field:    prefixName(prefix, field.Name),
				Classify: fmt.Sprintf("Array[%s]", fieldClassify.Name()),
				Pos:      positions,
				Valid:    validate,
				Comment:  field.Tag.Get("comment"),
			})
			if fieldClassify.Kind() == reflect.Struct {
				data = append(data, buildDocs(prefixName("--", prefixName(prefix, field.Name)), fieldClassify, tags)...)
			}
		case reflect.Map:
			var position = fieldPosition(field, tags)
			if len(position) < 1 {
				continue
			}
			var validate = fieldValidate(field)
			data = append(data, &docs{
				Field:    prefixName(prefix, field.Name),
				Classify: "Object[Map]",
				Pos:      position,
				Valid:    validate,
				Comment:  field.Tag.Get("comment"),
			})
		default:
			fmt.Println("解析文档字段失败")
			fmt.Println(field.Type.Kind())
			fmt.Println(field)
		}
	}
	return data
}

func prefixName(prefix string, name string) string {
	if len(prefix) < 1 {
		return name
	}
	str := strings.Join([]string{prefix, name}, ".")
	return strings.Replace(str, "-.", "-", -1)
}

func removePtr(value reflect.Type) reflect.Type {
	if value.Kind() == reflect.Ptr {
		value = value.Elem()
	}
	return value
}

func baseClassifyDocs(value reflect.Type, tags []string, realField ...reflect.StructField) []*docs {
	if len(realField) < 1 {
		return []*docs{
			{
				Classify: value.Name(),
			},
		}
	}
	field := realField[0]
	return []*docs{
		{
			Field:    field.Name,
			Classify: removePtr(field.Type).Name(),
			Pos:      fieldPosition(field, tags),
			Valid:    fieldValidate(field),
			Comment:  field.Tag.Get("comment"),
		},
	}
}

func fieldPosition(field reflect.StructField, tags []string) []*apiPosition {
	var items []*apiPosition
	for key := range tags {
		tagValue, ok := field.Tag.Lookup(tags[key])
		if ok {
			items = append(items, &apiPosition{
				Position: strings.TrimSpace(tags[key]),
				Name:     strings.TrimSpace(tagValue),
			})
		}
	}
	return items
}

func fieldValidate(field reflect.StructField) []string {
	validate := strings.Split(field.Tag.Get("binding"), ",")
	if len(validate) == 1 && validate[0] == "" {
		validate = []string{}
	}
	return validate
}

func CreateDocsServer() *DocsServer {
	return &DocsServer{}
}

type DocsServer struct{}

func (d *DocsServer) Show(ctx *gin.Context) {
	ctx.HTML(200, "docs.tmpl", gin.H{
		"docs": routerGroup,
	})
}
